// (C) 2022 Intel Corporation. All rights reserved.
// Your use of Intel Corporation's design tools, logic functions and other 
// software and tools, and its AMPP partner logic functions, and any output 
// files from any of the foregoing (including device programming or simulation 
// files), and any associated documentation or information are expressly subject 
// to the terms and conditions of the Intel Program License Subscription 
// Agreement, Intel FPGA IP License Agreement, or other applicable 
// license agreement, including, without limitation, that your use is for the 
// sole purpose of programming logic devices manufactured by Intel and sold by 
// Intel or its authorized distributors.  Please refer to the applicable 
// agreement for further details.

`timescale 1 ns / 1 ps
`default_nettype none

`define USE_LVDS
`define CRC_EN

module smbus_relay_mst #(
    parameter CLOCK_PERIOD_PS       = 10000,        // period of the input 'clock', in picoseconds (default 10,000 ps = 100 MHz)
    parameter BUS_SPEED_KHZ         = 100          // bus speed of the slowest device on the slave SMBus interface, in kHz (must be 100, 400, or 1000)
) (
    input  wire                            clock,              // master clock for this block
    input  wire                            i_resetn,           // master reset, must be de-asserted synchronously with clock
    
    //ioc frames
    output reg  [3:0]                      ioc_frame_o,
    input  wire [3:0]                      ioc_frame_i,
    
    input  wire                            ia_master_scl,      // asynchronous input from the SCL pin of the master interface
    output logic                           o_master_scl_oe,    // when asserted, drive the SCL pin of the master interface low
    input  wire                            ia_master_sda,      // asynchronous input from the SDA pin of the master interface
    output logic                           o_master_sda_oe,    // when asserted, drive the SDA pin of the master interface low
    
    output                                 stretch_timeout

);

 
    ///////////////////////////////////////
    // Calculate timing parameters
    ///////////////////////////////////////

    // minimum time to hold the SCLK signal low, in clock cycles
    localparam int SCLK_HOLD_MIN_COUNT =
        BUS_SPEED_KHZ == 100 ? ((5000000 + CLOCK_PERIOD_PS - 1) / CLOCK_PERIOD_PS) - 6:
        BUS_SPEED_KHZ == 400 ? ((1300000 + CLOCK_PERIOD_PS - 1) / CLOCK_PERIOD_PS) - 6:
                               (( 500000 + CLOCK_PERIOD_PS - 1) / CLOCK_PERIOD_PS) - 6 ;
    localparam int SCLK_HOLD_COUNTER_BIT_WIDTH = $clog2(SCLK_HOLD_MIN_COUNT)+1;             // counter will count from SCLK_HOLD_MIN_COUNT-1 downto -1

    localparam int NOISE_FILTER_MIN_CLOCK_CYCLES = ((50000 + CLOCK_PERIOD_PS - 1) / CLOCK_PERIOD_PS);
    localparam int NUM_FILTER_REGS = NOISE_FILTER_MIN_CLOCK_CYCLES >= 2 ? NOISE_FILTER_MIN_CLOCK_CYCLES : 2;    // always use at least 2 filter registers, to eliminate single-cycle pulses that might be caused during slow rising/falling edges
    
    localparam int SETUP_TIME_COUNT = 
        BUS_SPEED_KHZ == 100 ? 8:
        BUS_SPEED_KHZ == 400 ? 4 :4;
     enum logic [3:0] {
    idle,
    start,
    start_rcv,
    stop,
    stop_rcv,
    bit_rcv,
    data_0,
    data_1
    } ioc_frame_local, ioc_frame_remote;
    
    `ifdef USE_LVDS
    
    //TX
    reg lvds_sample_count_en;
    reg [4:0] lvds_sample_count; //ioc_frame event freq = 20M, lvds sample is 20/12M
    
    always@(posedge clock or negedge i_resetn)
    if(!i_resetn) begin
        lvds_sample_count <= 'b0;
        lvds_sample_count_en <= 'b0;
        ioc_frame_o <= 'b0;
    end
      else begin
        if(lvds_sample_count == 5'd11) begin
            lvds_sample_count_en <= 'b0;
            lvds_sample_count <= 'b0;
            ioc_frame_o <= 'b0;
        end
        else if(ioc_frame_local != idle) begin
            lvds_sample_count_en <= 1'b1;
            case(ioc_frame_local)
                start:     ioc_frame_o <= 4'h1;
                stop:      ioc_frame_o <= 4'h3;
                bit_rcv:   ioc_frame_o <= 4'h5;
                data_0:    ioc_frame_o <= 4'h6;
                data_1:    ioc_frame_o <= 4'h7;
            endcase
        end
        else if(lvds_sample_count_en) begin
            lvds_sample_count <= lvds_sample_count + 1'b1;
        end
    end
    
    //RX    
    always@(*)
        case(ioc_frame_i)
        4'h0: ioc_frame_remote = idle;
        4'h2: ioc_frame_remote = start_rcv;
        4'h4: ioc_frame_remote = stop_rcv;
        4'h5: ioc_frame_remote = bit_rcv;
        4'h6: ioc_frame_remote = data_0;
        4'h7: ioc_frame_remote = data_1;
        default: ioc_frame_remote = idle;
    endcase
    
    `else
        
        assign ioc_frame_remote = ioc_frame_i;
        
        always @ (*) begin
            ioc_frame_o = ioc_frame_local;
        end
        
    `endif
        
        
        
    ///////////////////////////////////////
    // Synchronize asynchronous SMBus input signals to clock and detect edges and start/stop conditions
    ///////////////////////////////////////

    // synchronized, metastability hardened versions of the scl and sda input signals for internal use in the rest of this module
    logic master_scl;
    logic master_sda;
    
    // detection of positive and negative edges, asserted synchronous with the first '1' or '0' on the master/slave scl signals defined below
    logic master_scl_posedge;
    logic master_scl_negedge;
    logic master_sda_posedge;
    logic master_sda_negedge;

    // SDA signals are delayed by 1 extra clock cycle, to provide a small amount of hold timing when sampling SDA on the rising edge of SCL
    async_input_filter #(
        .NUM_METASTABILITY_REGS (2),
        .NUM_FILTER_REGS        (NUM_FILTER_REGS)
    ) sync_master_scl_inst (
        .clock                  ( clock ),
        .ia_async_in            ( ia_master_scl ),
        .o_sync_out             ( master_scl ),
        .o_rising_edge          ( master_scl_posedge ),
        .o_falling_edge         ( master_scl_negedge )
    );

    async_input_filter #(
        .NUM_METASTABILITY_REGS (2),
        .NUM_FILTER_REGS        (NUM_FILTER_REGS+1)
    ) sync_master_sda_inst (
        .clock                  ( clock ),
        .ia_async_in            ( ia_master_sda ),        
        .o_sync_out             ( master_sda ),
        .o_rising_edge          ( master_sda_posedge ),
        .o_falling_edge         ( master_sda_negedge )
    );
    
    
    // detect start and stop conditions on the master bus, asserted 1 clock cycle after the start/stop condition actually occurs
    logic master_start;
    logic master_stop;
    logic master_scl_dly;       // delayed version of master_scl by 1 clock cycle
    
    always_ff @(posedge clock or negedge i_resetn) begin
        if (!i_resetn) begin
            master_start        <= '0;
            master_stop         <= '0;
            master_scl_dly      <= '0;
        end else begin
            master_start        <= master_scl & master_sda_negedge;      // falling edge on SDA while SCL high is a start condition
            master_stop         <= master_scl & master_sda_posedge;      // rising edge on SDA while SCL high is a stop condition
            master_scl_dly      <= master_scl;
        end
    end
    

    ///////////////////////////////////////
    // Track the 'phase' of the master SMBus
    ///////////////////////////////////////
    
    enum {
        SMBMASTER_STATE_IDLE                ,
        SMBMASTER_STATE_START               ,
        SMBMASTER_STATE_MASTER_ADDR         ,
        SMBMASTER_STATE_SLAVE_ADDR_ACK      ,
        SMBMASTER_STATE_MASTER_CMD          ,
        SMBMASTER_STATE_SLAVE_CMD_ACK       ,
        SMBMASTER_STATE_MASTER_WRITE        ,
        SMBMASTER_STATE_SLAVE_WRITE_ACK     ,
        SMBMASTER_STATE_SLAVE_READ          ,
        SMBMASTER_STATE_MASTER_READ_ACK     ,
        SMBMASTER_STATE_STOP                ,
        SMBMASTER_STATE_STOP_THEN_START      
    }                               master_smbstate;                // current state of the master SMBus
        
    logic [3:0]                     master_bit_count;               // number of bits received in the current byte (starts at 0, counts up to 8)
    logic                           clear_master_bit_count;         // reset the master_bit_count to 0 when asserted
    logic [7:0]                     master_byte_in;                 // shift register to store incoming bits, shifted in starting at position 0 and shifting left (so first bit ends up as the msb)
    logic                           command_rd_wrn;                 // captured during the slave address phase, indicates if the current command is a READ (1) or WRITE (0) command
    logic                           master_read_nack;               // capture the ack/nack status from the master after a read data byte has been sent
    logic                           master_read_nack_valid;         // used to ensure master_read_nack is only captured on the first rising edge of clock after read data has been sent
    logic                           master_triggered_start;         // used to indicates that the next start condition is a repeated start

    
    enum {
        RELAY_STATE_IDLE                                    ,
        RELAY_STATE_START_WAIT_TIMEOUT                      ,
        RELAY_STATE_START_WAIT_SCL_LOW                     ,
        RELAY_STATE_START_MTOS_WAIT_START_RCV        ,
        RELAY_STATE_STOP_WAIT_TIMEOUT       ,
        RELAY_STATE_STOP,
        RELAY_STATE_WAIT_STOP_RCV                           ,
        // in all 'MTOS' (Master to Slave) states, the master is driving the SDA signal to the slave, so SDA must be relayed from the master bus to the slave bus (or sometimes a 'captured' version of SDA)
        RELAY_STATE_MTOS_SCL_LOW_WAIT_TIMEOUT              ,
        RELAY_STATE_MTOS_WAIT_SCL_HIGH                     ,
        RELAY_STATE_MTOS_WAIT_BIT_RCV                     ,
        RELAY_STATE_MTOS_WAIT_SCL_LOW                      ,
        // in all 'STOM' (Slave to Master) states, the slave is driving the SDA signal to the master, so SDA must be relayed from the slave bus to the master bus (or sometimes a 'captured' version of SDA)
        RELAY_STATE_STOM_WAIT_EVENT             ,
        RELAY_STATE_STOM_SCL_LOW_WAIT_SETUP_TIMEOUT              ,
        RELAY_STATE_STOM_WAIT_SCL_HIGH           ,
        RELAY_STATE_STOM_WAIT_SCL_LOW            //,
    }                                           relay_state;                    // current state of the relay between the master and slave busses
    logic [SCLK_HOLD_COUNTER_BIT_WIDTH-1:0]     slave_scl_hold_count;           // counter to determine how long to hold the slave scl signal low/high, counts down to -1 then waits to be restarted (so msb=1 indicates the counter has timed out)
    logic                                       slave_scl_hold_count_restart;   // combinatorial signal, reset the slave_scl_hold_count counter and start a new countdown
    logic                                       slave_scl_hold_count_timeout;   // combinatorial signal (copy of msb of slave_scl_hold_count), indicates the counter has reached it's timeout value and is not about to be restarted
    
    logic [19:0]                                event_waiting_count;
    logic                                       event_waiting_count_restart;

    
logic [4:0]                                 setup_timeout;        
        
    
    always_ff @(posedge clock or negedge i_resetn) begin
        if (!i_resetn) begin
        
            master_smbstate             <= SMBMASTER_STATE_IDLE;
            master_bit_count            <= 4'h0;
            clear_master_bit_count      <= '0;
            master_byte_in              <= 8'h00;
            command_rd_wrn              <= '0;
            master_read_nack            <= '0;
            master_read_nack_valid      <= '0;
            master_triggered_start      <= '0;
            ioc_frame_local             <= idle;
        end
        else if(event_waiting_count == 20'b0) begin
            master_smbstate             <= SMBMASTER_STATE_IDLE;
            master_bit_count            <= 4'h0;
            clear_master_bit_count      <= '0;
            master_byte_in              <= 8'h00;
            command_rd_wrn              <= '0;
            master_read_nack            <= '0;
            master_read_nack_valid      <= '0;
            master_triggered_start      <= '0;
            ioc_frame_local             <= idle;
        end
        else begin
        
            case ( master_smbstate )
                // IDLE state
                // This is the reset state.  Wait here until a valid START condition is detected on the master smbus
                SMBMASTER_STATE_IDLE: begin
                    if (master_start) begin                  // only way to leave the idle state is if we detect a 'start' condition
                        master_smbstate <= SMBMASTER_STATE_START;
                        ioc_frame_local <= start;
                    end
                    else begin
                        ioc_frame_local <= idle;
                    end
                    clear_master_bit_count <= '1;                               // hold the bit counter at 0 until we have exited the idle state
                end
                
                // START state
                // A start condition was detected on the master bus, we must stay here and clockstretch the master bus as required until the slave bus has 'caught up' and also issued a start condition
                SMBMASTER_STATE_START: begin
                    if (master_stop) begin                                      // start followed immediately by stop is strange, but not necessarily invalid
                        master_smbstate <= SMBMASTER_STATE_STOP;
                    end 
                    else begin
                        if(master_scl == 1'b0) begin
                            master_smbstate <= SMBMASTER_STATE_MASTER_ADDR;
                        end
                    end
                    ioc_frame_local <= idle;
                    clear_master_bit_count <= '1;                               // hold the bit counter at 0
                end


                // MASTER_ADDR state
                // receive the 7 bit slave addres and the read/write bit
                // leave this state when all 8 bits have been received and the clock has been driven low again by the master
                SMBMASTER_STATE_MASTER_ADDR: begin
                    if (master_stop || master_start) begin                      // unexpected stop/start condition, ignore further master bus activity until we can issue a 'stop' condition on the slave bus
                        master_smbstate <= SMBMASTER_STATE_STOP;
                        ioc_frame_local <= idle;
                    end 
                    else begin                        
                        if (master_scl_negedge == 1'b1 && master_bit_count == 4'h8) begin   // we have received all 8 data bits, time for the ACK
                                master_smbstate <= SMBMASTER_STATE_SLAVE_ADDR_ACK;            // not our address, send a 'stop' condition on the slave bus and stop forwarding from the master until the next 'start'
                                ioc_frame_local <= idle;
                        end

                        if(master_scl_negedge == 1'b1) begin    
                            if(master_sda == 1'b0)
                                ioc_frame_local <= data_0;
                            else
                                ioc_frame_local <= data_1;
                        end
                        else begin
                            ioc_frame_local <= idle;
                        end
                        
                        clear_master_bit_count <= '0;                           // do not reset the bit counter in this state, it will be reset during the next ACK state
                    end

                end
                
                // SLAVE_ADDR_ACK state
                // we may be driving the master_scl signal during this state to clock-stretch while awaiting an ACK from the slave bus
                // always enter this state after an scl falling edge
                // leave this state when the ack/nack bit has been sent and the clock has gone high then been drivien low again by the master
                SMBMASTER_STATE_SLAVE_ADDR_ACK: begin
                    if (master_stop || master_start) begin                      // unexpected stop/start condition, ignore further master bus activity until we can issue a 'stop' condition on the slave bus
                        master_smbstate <= SMBMASTER_STATE_STOP;
                        ioc_frame_local <= idle;
                    end 
                    else begin
                        if (master_scl_negedge) begin                           // ack/nack sent on clock rising edge, then need to wait for clock to go low again before leaving this state so it's safe to stop driving sda
                            if(!o_master_sda_oe) begin
                                master_smbstate <= SMBMASTER_STATE_STOP;
                                ioc_frame_local <= idle;
                            end 
                            else if (command_rd_wrn) begin
                                master_smbstate <= SMBMASTER_STATE_SLAVE_READ;              // this is a read command, start sending data back from slave
                            end 
                            else begin
                                master_smbstate <= SMBMASTER_STATE_MASTER_CMD;              // receive the command on the next clock cycle
                            end
                            ioc_frame_local <= bit_rcv;
                        end
                        else begin
                            ioc_frame_local <= idle;
                        end
                    end
                    clear_master_bit_count <= '1;                               // hold the counter at 0 through this state, so it has the correct value at the start of the next state
                    
                end
                        
                // MASTER_CMD state
                // receive the 8 bit command (the first data byte after the address is called the 'command')
                // always enter this state after an scl falling edge
                // leave this state when all 8 bits have been received and the clock has been driven low again by the master
                SMBMASTER_STATE_MASTER_CMD: begin
                    if (master_stop || master_start) begin                      // unexpected stop/start condition, ignore further master bus activity until we can issue a 'stop' condition on the slave bus
                        master_smbstate <= SMBMASTER_STATE_STOP;
                        ioc_frame_local <= idle;
                    end else begin
                        if (master_scl_negedge == 1'b1 && master_bit_count == 4'h8) begin   // we have received all 8 data bits, time for the ACK
                            master_smbstate <= SMBMASTER_STATE_SLAVE_CMD_ACK;
                            ioc_frame_local <= idle;
                        end
                        
                        if(master_scl_negedge == 1'b1) begin    
                            if(master_sda == 1'b0)
                                ioc_frame_local <= data_0;
                            else
                                ioc_frame_local <= data_1;
                        end
                        else begin
                            ioc_frame_local <= idle;
                        end
                    end
                    clear_master_bit_count <= '0;                           // do not reset the bit counter in this state, it will be reset during the next ACK state
                end

                // SLAVE_CMD_ACK or SLAVE_WRITE_ACK state
                // we may be driving the master_scl signal during this state to clock-stretch while awaiting an ACK from the slave bus
                // always enter this state after an scl falling edge
                // leave this state when the ack/nack bit has been sent and the clock has been drivien low again by the master 
                SMBMASTER_STATE_SLAVE_CMD_ACK,
                SMBMASTER_STATE_SLAVE_WRITE_ACK: begin
                    if (master_stop || master_start) begin                      // unexpected stop/start condition, ignore further master bus activity until we can issue a 'stop' condition on the slave bus
                        master_smbstate <= SMBMASTER_STATE_STOP;
                        ioc_frame_local <= idle;
                    end 
                    else begin
                        if (master_scl_negedge) begin                           // ack/nack sent on clock rising edge, then need to wait for clock to go low again before leaving this state so it's safe to stop driving sda
                            if(!o_master_sda_oe) begin
                                master_smbstate <= SMBMASTER_STATE_STOP;
                                ioc_frame_local <= idle;
                            end 
                            else begin
                                master_smbstate <= SMBMASTER_STATE_MASTER_WRITE;
                            end
                            ioc_frame_local <= bit_rcv;
                        end
                        else begin
                            ioc_frame_local <= idle;
                        end
                    end
                    clear_master_bit_count <= '1;                               // hold the counter at 0 through this state, so it has the correct value (0) at the start of the next state        
                end

                // MASTER_WRITE state
                // receive a byte to write to the slave device
                // always enter this state after an scl falling edge
                // for non-whitelisted write commands, expect a 'restart' condition (which will allow a read command to begin) or abort the command by returning to the IDLE state
                // for whitelisted commands, allow the command to proceed
                SMBMASTER_STATE_MASTER_WRITE: begin
                    if (master_stop) begin                                      // unexpected stop condition, ignore further master bus activity until we can issue a 'stop' condition on the slave bus
                        master_smbstate <= SMBMASTER_STATE_STOP;
                        ioc_frame_local <= idle;
                    end
                    else if(master_start) begin
                        master_smbstate <= SMBMASTER_STATE_START;
                        ioc_frame_local <= start;
                    end
                    else begin
                        if(master_scl_negedge == 1'b1) begin    
                            if (master_bit_count == 4'h8) 
                                master_smbstate <= SMBMASTER_STATE_SLAVE_WRITE_ACK;
                                
                            if(master_sda == 1'b0)
                                ioc_frame_local <= data_0;
                            else
                                ioc_frame_local <= data_1;
                        end
                        else begin
                            ioc_frame_local <= idle;
                        end               
                    end

                    clear_master_bit_count <= '0;                           // do not reset the bit counter in this state, it will be reset during the next ACK state

                end

                // SLAVE_READ state
                // receive a byte from the slave device and send it to the master
                // always enter this state after an scl falling edge
                SMBMASTER_STATE_SLAVE_READ: begin
                    if (master_stop || master_start) begin                      // unexpected stop/start condition, ignore further master bus activity until we can issue a 'stop' condition on the slave bus
                        master_smbstate <= SMBMASTER_STATE_STOP;
                        ioc_frame_local <= idle;
                    end else begin
                        if (master_bit_count == 4'h8 && master_scl_negedge == 1'b1) begin   // we have sent all 8 data bits, time for the ACK (from the master)
                            master_smbstate <= SMBMASTER_STATE_MASTER_READ_ACK;
                        end
                        
                        if(master_scl_negedge == 1'b1) begin
                            ioc_frame_local <= bit_rcv;
                        end
                        else begin
                            ioc_frame_local <= idle;
                        end               
                    end
                    clear_master_bit_count <= '0;                           // do not reset the bit counter in this state, it will be reset during the next ACK state
                end
                
                // MASTER_READ_ACK
                // always enter this state after an scl falling edge
                // leave this state when the ack/nack bit has been received and the clock has been drivien low again by the master 
                SMBMASTER_STATE_MASTER_READ_ACK: begin
                    if (master_stop || master_start) begin                      // unexpected stop/start condition, ignore further master bus activity until we can issue a 'stop' condition on the slave bus
                        master_smbstate <= SMBMASTER_STATE_STOP;
                        ioc_frame_local <= idle;
                    end else begin
                        if (master_scl_negedge) begin
                            if (~master_read_nack) begin                        // if we received an ack (not a nack) from the master, continue reading data from slave
                                master_smbstate <= SMBMASTER_STATE_SLAVE_READ;
                            end else begin                                      // on a nack, send a stop condition on the slave bus and wait for a stop on the master bus
                                master_smbstate <= master_smbstate; //wait for stop
                                ioc_frame_local <= idle;
                            end
                        end
                        
                        if(master_scl_negedge == 1'b1) begin    
                            if(master_sda == 1'b0)
                                ioc_frame_local <= data_0;
                            else
                                ioc_frame_local <= data_1;
                        end
                        else begin
                            ioc_frame_local <= idle;
                        end
                    end
                    clear_master_bit_count <= '1;                               // hold the counter at 0 through this state, so it has the correct value (0) at the start of the next state
                end

                // STOP
                // Enter this state to indicate a STOP condition should be sent to the slave bus
                // Once the STOP has been sent on the slave bus, we return to the idle state and wait for another start condition
                // We can enter this state if a stop condition has been received on the master bus, or if we EXPECT a start condition on the master busses
                // We do not wait to actually see a stop condition on the master bus before issuing the stop on the slave bus and proceeding to the IDLE state
                SMBMASTER_STATE_STOP: begin
                    if (master_start) begin
                        master_smbstate <= SMBMASTER_STATE_STOP_THEN_START;     // we may receive a valid start condition while waiting to send a stop, we need to track that case
                    end 
                    else if (relay_state == RELAY_STATE_STOP) begin
                        ioc_frame_local <= stop;
                    end
                    else if (relay_state == RELAY_STATE_IDLE) begin
                        master_smbstate <= SMBMASTER_STATE_IDLE;
                    end
                    else begin
                        ioc_frame_local <= idle;
                    end
                    clear_master_bit_count <= '1;                               // hold the counter at 0 through this state, so it has the correct value (0) at the start of the next state
                end

                // STOP_THEN_START
                // While waiting to send a STOP on the slave bus, we receive a new start condition on the master busses
                // Must finish sending the stop condition on the slave bus, then send a start condition on the slave bus
                SMBMASTER_STATE_STOP_THEN_START: begin
                    if (relay_state == RELAY_STATE_STOP) begin
                        ioc_frame_local <= stop;
                    end
                    else begin
                        ioc_frame_local <= idle;
                    end
                    
                    if (relay_state == RELAY_STATE_START_MTOS_WAIT_START_RCV) begin
                        master_smbstate <= SMBMASTER_STATE_START;
                        ioc_frame_local <= start;
                    end
                    clear_master_bit_count <= '1;                               // hold the counter at 0 through this state, so it has the correct value (0) at the start of the next state
                end

                
                default: begin                                                  // illegal state, should never get here
                    master_smbstate <= SMBMASTER_STATE_IDLE;
                    clear_master_bit_count <= '1;
                    ioc_frame_local <= idle;
                end
                
            endcase  

            // counter for bits received on the master bus
            // the master SMBus state machine will clear the counter at the appropriate times, otherwise it increments on every master scl rising edge
            if (clear_master_bit_count || master_start) begin       // need to reset the counter on a start condition to handle the repeated start (simpler than assigning clear signal in this one special case)
                master_bit_count <= 4'h0;
            end else begin
                if (master_scl_posedge) begin
                    master_bit_count <= master_bit_count + 4'b0001;
                end
            end
            
            // shift register to store incoming bits from the master bus
            // shifts on every clock edge regardless of bus state, rely on master_smbstate and master_bit_count to determine when a byte is fully formed
            if (master_scl_posedge) begin
                master_byte_in[7:0] <= {master_byte_in[6:0], master_sda};
            end
            
            if ( (master_smbstate == SMBMASTER_STATE_MASTER_ADDR) && (master_bit_count == 4'h8) ) begin  // 8th bit is captured on the next clock cycle, so that's when it's safe to check the full byte contents
                command_rd_wrn <= master_byte_in[0];
            end
            
            // capture the read data ACK/NACK from the master after read data has been sent
            // make sure to only capture ack/nack on the first rising edge of SCL using the master_read_nack_valid signal
            if (master_smbstate == SMBMASTER_STATE_MASTER_READ_ACK) begin
                if (master_scl_posedge) begin
                    if (!master_read_nack_valid) begin
                        master_read_nack <= master_sda;
                    end
                    master_read_nack_valid <= '1;
                end
            end else begin
                master_read_nack_valid <= '0;
                master_read_nack <= '1;
            end
            
            // repeated start can only be used to switch from write mode to read mode in SMBus protocol
            if (master_smbstate == SMBMASTER_STATE_MASTER_CMD) begin
                master_triggered_start <= '1;
            end else if ((master_smbstate == SMBMASTER_STATE_STOP) || (master_smbstate == SMBMASTER_STATE_IDLE)) begin            
                master_triggered_start <= '0;
            end                        
        end
        
    end
        
    ///////////////////////////////////////
    // Determine the state of the relay, and drive the slave and master SMBus signals based on the state of the master and slave busses
    ///////////////////////////////////////

    always_ff @(posedge clock or negedge i_resetn) begin
        if (!i_resetn) begin

            relay_state                     <= RELAY_STATE_IDLE;
            o_master_scl_oe                 <= '0;
            o_master_sda_oe                 <= '0;
            slave_scl_hold_count            <= {SCLK_HOLD_COUNTER_BIT_WIDTH{1'b0}};
            setup_timeout                   <= 5'b0;
            
        end else begin
        
            case ( relay_state )
            
                // IDLE state
                // waiting for a start condition on the master busses
                RELAY_STATE_IDLE: begin
                    if (master_smbstate == SMBMASTER_STATE_START) begin
                        relay_state <= RELAY_STATE_START_WAIT_TIMEOUT;
                    end
                    o_master_scl_oe <= '0;
                    o_master_sda_oe <= '0;
                end

                // START_WAIT_TIMEOUT
                // Start condition has been received on the master bus
                // Drive a start condition on the slave bus (SDA goes low while SCL is high) and wait for a timeout
                // If master SCL goes low during this state, hold it there to prvent further progress on the master bus until the slave bus can 'catch up'
                RELAY_STATE_START_WAIT_TIMEOUT: begin
                        relay_state <= RELAY_STATE_START_MTOS_WAIT_START_RCV;
                    o_master_scl_oe <= '0;             // if master SCL goes low, we hold it low
                    o_master_sda_oe <= '0; 
                end

                // START_WAIT_MSCL_LOW
                // Start condition has been received on the master bus
                // Continue to drive a start condition on the slave bus (SDA low while SCL is high) and wait for master scl to go low
                // If master SCL is low during this state, continue to hold it there to prvent further progress on the master bus until the slave bus can 'catch up'
                RELAY_STATE_START_WAIT_SCL_LOW: begin
                    if (~master_scl) begin
                        relay_state <= RELAY_STATE_MTOS_WAIT_SCL_HIGH;     // after a start, the master is driving the bus
                    end 
                    else if (master_smbstate == SMBMASTER_STATE_STOP) begin        // stop right after start may not be legal, but safter to handle this case anyway
                        relay_state <= RELAY_STATE_STOP_WAIT_TIMEOUT;
                    end
                    o_master_scl_oe <= '0;             // if master SCL goes low, we hold it low
                    o_master_sda_oe <= '0; 
                end

                // MTOS_MSCL_LOW_WAIT_SSCL_LOW
                // This state exists to provide some hold time on the slave SDA signal, we drive slave SCL low and wait until we observe it low before proceeding
                // While in this state, we hold the old value of o_slave_sda_oe
                // Clockstretch the master SCL (which is also low) during this state
                RELAY_STATE_START_MTOS_WAIT_START_RCV: begin
                     if(event_waiting_count == 20'b0) begin //timeout
                        relay_state <= RELAY_STATE_IDLE;                                                            
                    end
                    else if(ioc_frame_remote == start_rcv && ~master_scl) begin
                        relay_state <= RELAY_STATE_MTOS_WAIT_SCL_HIGH;
                    end
						  else if(ioc_frame_remote == start_rcv && master_scl) begin
		                  relay_state <= RELAY_STATE_START_WAIT_SCL_LOW;
						  end
                    o_master_scl_oe <= ~master_scl;             // we know master SCL is low here, we continue to hold it low
                    o_master_sda_oe <= '0; 
                end
                
                // MTOS_WAIT_MSCL_HIGH
                // Master is driving the bus, SDA from the master bus to the slave bus
                // Release master SCL and wait for it to go high, while continuing to hold slave scl low
                RELAY_STATE_MTOS_WAIT_SCL_HIGH: begin
                    if (master_scl) begin
                        relay_state <= RELAY_STATE_MTOS_WAIT_BIT_RCV;
                    end
                    o_master_scl_oe <= '0;
                    o_master_sda_oe <= '0; 
                end

                
                // MTOS_WAIT_MSCL_LOW
                // Master is driving the bus, SDA from the master bus to the slave bus
                // Wait for master SCL to go low, then hold it low (clockstretch) and proceed to state where we drive the slave scl low
                RELAY_STATE_MTOS_WAIT_SCL_LOW: begin
 
                      begin
                        if (~master_scl) begin      // need to look at a delayed version of master scl, to give master_smbstate state machine time to update

									 begin

                                relay_state <= RELAY_STATE_MTOS_SCL_LOW_WAIT_TIMEOUT;
                            end
                        end
                    end
                    o_master_scl_oe <= '0;             // if master SCL is low, we hold it low
                    o_master_sda_oe <= '0; 
                end                
                // MTOS_WAIT_SSCL_HIGH
                // Master is driving the bus, SDA from the master bus to the slave bus
                // Release slave SCL and wait for it to go high
                // If master SCL goes low again during this state, hold it there to prvent further progress on the master bus until the slave bus can 'catch up'
                RELAY_STATE_MTOS_WAIT_BIT_RCV: begin
                     if(event_waiting_count == 20'b0) begin //timeout
                        relay_state <= RELAY_STATE_IDLE;                                                            
                    end    
                     else if (master_smbstate == SMBMASTER_STATE_START) begin
                        relay_state <= RELAY_STATE_START_WAIT_TIMEOUT;
                    end else if (master_smbstate == SMBMASTER_STATE_STOP) begin
                            relay_state <= RELAY_STATE_STOP_WAIT_TIMEOUT;
                    end
                    else if(ioc_frame_remote == bit_rcv && ~master_scl_dly) begin
								if( (master_smbstate == SMBMASTER_STATE_SLAVE_ADDR_ACK) ||
                            (master_smbstate == SMBMASTER_STATE_SLAVE_CMD_ACK) ||
                            (master_smbstate == SMBMASTER_STATE_SLAVE_WRITE_ACK) ||
                            (master_smbstate == SMBMASTER_STATE_SLAVE_READ) ) begin
									 relay_state <= RELAY_STATE_STOM_WAIT_EVENT;
									 end
								else begin
                        relay_state <= RELAY_STATE_MTOS_SCL_LOW_WAIT_TIMEOUT;
                        end
						  end
						  else if(ioc_frame_remote == bit_rcv && master_scl_dly) begin
						      relay_state <= RELAY_STATE_MTOS_WAIT_SCL_LOW;
						  end
                    o_master_scl_oe <= ~master_scl;             // if master SCL goes low, we hold it low
                    o_master_sda_oe <= '0; 
                end

                // MTOS_SSCL_LOW_WAIT_TIMEOUT
                // Master is driving the bus, SDA from the master bus to the slave bus
                // We are driving slave SCL low, wait for a timeout before allowing it to go high
                // Clockstretch the master SCL (which is also low) during this state
                RELAY_STATE_MTOS_SCL_LOW_WAIT_TIMEOUT: begin
                        relay_state <= RELAY_STATE_MTOS_WAIT_SCL_HIGH;
                    o_master_scl_oe <= ~master_scl;             // we know master SCL is low here, we continue to hold it low
                    o_master_sda_oe <= '0; 
                end

                // STOM_MSCL_LOW_WAIT_SSCL_LOW
                // We start driving slave SCL low, wait to observe it actually going low before releasing the o_slave_sda_oe signal
                // This state exists to provide some hold timing on slave SDA after slave SCL goes low
                // Clockstretch the master SCL (which is also low) during this state
                RELAY_STATE_STOM_WAIT_EVENT: begin
                    if(event_waiting_count == 20'b0) begin //timeout
                        relay_state <= RELAY_STATE_IDLE;                                
                        o_master_scl_oe <= '0;             // we know master SCL is low here, we continue to hold it low
                        o_master_sda_oe <= '0;              // drive sda from the slave bus to the master bus                            
                    end
                    else if(ioc_frame_remote == data_0) begin
                        relay_state <= RELAY_STATE_STOM_SCL_LOW_WAIT_SETUP_TIMEOUT;                                
                        o_master_scl_oe <= '1;             // we know master SCL is low here, we continue to hold it low
                        o_master_sda_oe <= '1;              // drive sda from the slave bus to the master bus
                    end
                    else if(ioc_frame_remote == data_1) begin
                        relay_state <= RELAY_STATE_STOM_SCL_LOW_WAIT_SETUP_TIMEOUT;                                
                        o_master_scl_oe <= '1;             // we know master SCL is low here, we continue to hold it low
                        o_master_sda_oe <= '0;              // drive sda from the slave bus to the master bus
                    end
                    else begin
                        o_master_scl_oe <= '1;             // we know master SCL is low here, we continue to hold it low
                        o_master_sda_oe <= o_master_sda_oe;
                    end
                end

                // STOM_SSCL_LOW_WAIT_TIMEOUT
                // Slave is driving data, SDA from the slave bus to the master
                // We are driving slave SCL low, wait for a timeout before allowing it to go high
                // Clockstretch the master SCL (which is also low) during this state
                RELAY_STATE_STOM_SCL_LOW_WAIT_SETUP_TIMEOUT: begin
                    if(setup_timeout == SETUP_TIME_COUNT) begin
                        setup_timeout <= 5'b0;
                        relay_state <= RELAY_STATE_STOM_WAIT_SCL_HIGH;
                    end
                    else begin
                        setup_timeout <= setup_timeout + 5'd1;
                    end
                    o_master_scl_oe <= '1;             // we know master SCL is low here, we continue to hold it low
                    o_master_sda_oe <= o_master_sda_oe;
                end

                // STOM_SSCL_HIGH_WAIT_MSCL_HIGH
                // Slave is driving data, SDA from the slave bus to the master
                // Slave SCL is high, release master SCL and wait for it to go high
                // Continue to clockstretch (hold SCL low) on the master bus until we have received data from the slave bus
                RELAY_STATE_STOM_WAIT_SCL_HIGH: begin
                    if (master_scl) begin
                        relay_state <= RELAY_STATE_STOM_WAIT_SCL_LOW;
                    end
                    o_master_scl_oe <= '0;
                    o_master_sda_oe <= o_master_sda_oe;
                end
            
 
                // STOM_SSCL_HIGH_WAIT_MSCL_LOW
                // Slave is driving data, SDA from the slave bus to the master
                // Slave and master SCL are high, we are waiting for master SCL to go low
                RELAY_STATE_STOM_WAIT_SCL_LOW: begin
                    if (master_smbstate == SMBMASTER_STATE_STOP) begin
                        relay_state <= RELAY_STATE_STOP_WAIT_TIMEOUT;
                    end else begin
                        if (~master_scl_dly) begin      // need to look at a delayed version of master scl, to give master_smbstate state machine time to update
                            if ( master_smbstate == SMBMASTER_STATE_SLAVE_READ ) begin      // only in the SLAVE_READ state do we have two SMBus cycles in a row where the slave drives data to the master
                                relay_state <= RELAY_STATE_STOM_WAIT_EVENT;                                
                            end else begin
                                relay_state <= RELAY_STATE_MTOS_SCL_LOW_WAIT_TIMEOUT;
                            end
                        end
                    end
                    o_master_scl_oe <= '0;
                    o_master_sda_oe <= o_master_sda_oe;
                end

                // STOP_WAIT_SSCL_LOW
                // Sending a stop condition on the slave bus
                // Drive slave SCL low, wait to see it has gone low before driving SDA low (which happens in the next state)
                // Clockstretch the master bus if master SCL is driven low, to prevent the master bus from getting 'ahead' of the slave bus
                RELAY_STATE_STOP_WAIT_TIMEOUT: begin            
                    if (slave_scl_hold_count_timeout) begin
                        relay_state <= RELAY_STATE_STOP;
                    end
                    o_master_scl_oe <= 1'b1;             // clockstretch on the master bus if master scl is driven low
                    o_master_sda_oe <= '0;
                end

                RELAY_STATE_STOP: begin
                    relay_state <= RELAY_STATE_WAIT_STOP_RCV;
                    o_master_scl_oe <= ~master_scl;             // clockstretch on the master bus if master scl is driven low
                    o_master_sda_oe <= '0;    
                end
                
                RELAY_STATE_WAIT_STOP_RCV: begin
                    if(event_waiting_count == 20'b0) begin //timeout
                        relay_state <= RELAY_STATE_IDLE;                                
                    end
                    else if(ioc_frame_remote == stop_rcv) begin
                        if(master_smbstate == SMBMASTER_STATE_STOP_THEN_START)
                            relay_state <= RELAY_STATE_START_MTOS_WAIT_START_RCV;
                        else
                            relay_state <= RELAY_STATE_IDLE;
                    end
                    o_master_scl_oe <= ~master_scl;             // clockstretch on the master bus if master scl is driven low
                    o_master_sda_oe <= '0;
                end

                
                default: begin
                    relay_state <= RELAY_STATE_IDLE;
                    o_master_scl_oe <= '0;             // clockstretch on the master bus if master scl is driven low
                    o_master_sda_oe <= '0;
                end

            endcase
            
            // counter to determine how long to hold slave_scl low or high 
            // used when creating 'artificial' scl clock pulses on slave while clock stretching the master during ack and read data phases
            // counter counts from some positive value down to -1, thus checking the highest bit (sign bit) is adequate to determine if the count is 'done'
            if (slave_scl_hold_count_restart) begin
                slave_scl_hold_count <= SCLK_HOLD_MIN_COUNT[SCLK_HOLD_COUNTER_BIT_WIDTH-1:0] - {{SCLK_HOLD_COUNTER_BIT_WIDTH-1{1'b0}}, 1'b1}; 
            end else if (!slave_scl_hold_count[SCLK_HOLD_COUNTER_BIT_WIDTH-1]) begin        // count down to -1 (first time msb goes high) and then stop
                slave_scl_hold_count <= slave_scl_hold_count - {{SCLK_HOLD_COUNTER_BIT_WIDTH-1{1'b0}}, 1'b1};
            end

            if (!event_waiting_count_restart) begin
                event_waiting_count <= 20'd600000; 
            end 
            else begin        // count down to -1 (first time msb goes high) and then stop
                event_waiting_count <= event_waiting_count - 20'd1;
            end

            
        end
        
    end

    // when the msb of slave_scl_hold_count = 1, that indicates a negative number, which means the timeout has occurred
    assign slave_scl_hold_count_timeout = slave_scl_hold_count[SCLK_HOLD_COUNTER_BIT_WIDTH-1];
    
    assign stretch_timeout = slave_scl_hold_count_timeout;
    
    // determine when to reset the counter based on the current relay state
    // creatre this signal with combinatorial logic so counter will be reset as we enter the next state
    // we never have two states in a row that both require this counter
    assign slave_scl_hold_count_restart = ( (relay_state == RELAY_STATE_START_WAIT_TIMEOUT) ||
                                            (relay_state == RELAY_STATE_MTOS_SCL_LOW_WAIT_TIMEOUT) ||
                                            (relay_state == RELAY_STATE_STOP_WAIT_TIMEOUT) ||
                                            (relay_state == RELAY_STATE_STOM_SCL_LOW_WAIT_SETUP_TIMEOUT) ||
                                            (relay_state == RELAY_STATE_STOM_WAIT_SCL_HIGH)
                                          ) ? '0 : '1;
                                          
   
    assign event_waiting_count_restart = ( (relay_state == RELAY_STATE_START_MTOS_WAIT_START_RCV) ||
                                            (relay_state == RELAY_STATE_WAIT_STOP_RCV)    ||
                                            (relay_state == RELAY_STATE_MTOS_WAIT_BIT_RCV)    ||
                                            (relay_state == RELAY_STATE_STOM_WAIT_EVENT)    
                                          ) ? '1 : '0;

endmodule





        
                
                
        
        

    
